package com.wyy.multiredis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;

/**
 * @Date: 2023/12/13
 * @Author: wyy
 */
public class MultiRedisFactory implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        multiRedisProperties.getMultiRedis().forEach(redis -> {
            LettuceConnectionFactory connectionFactory = buildConnectionFactory(redis);
            BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(RedisTemplateFactory.class)
                    .addConstructorArgValue(connectionFactory)
                    .getBeanDefinition();
            registry.registerBeanDefinition(redis.getRedisTemplateName(), beanDefinition);
            log.info("register RedisTemplate:{} success", redis.getRedisTemplateName());
        });
    }
    public LettuceConnectionFactory buildConnectionFactory(MultiRedis multiRedis) {
        RedisConfiguration redisConfiguration = null;
        switch (multiRedis.getRedisMode()) {
            case single:
                redisConfiguration = singleConfig(multiRedis);
                break;
            case sentinel:
                redisConfiguration = sentinelConfig(multiRedis);
                break;
            case cluster:
                redisConfiguration = clusterConfig(multiRedis);
                break;
        }
        LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(redisConfiguration);
        connectionFactory.afterPropertiesSet();
        return connectionFactory;
    }


    private RedisStandaloneConfiguration singleConfig(RedisProperties redisProperties) {
        RedisStandaloneConfiguration standConfig = new RedisStandaloneConfiguration();
        standConfig.setHostName(redisProperties.getHost());
        standConfig.setPort(redisProperties.getPort());
        standConfig.setPassword(redisProperties.getPassword());
        standConfig.setDatabase(redisProperties.getDatabase());
        standConfig.setUsername(redisProperties.getUsername());
        return standConfig;
    }


    private RedisSentinelConfiguration sentinelConfig(RedisProperties redisProperties) {
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
        sentinelConfig.setMaster(redisProperties.getSentinel().getMaster());
        sentinelConfig.setSentinels(createSentinels(redisProperties.getSentinel()));
        sentinelConfig.setDatabase(redisProperties.getDatabase());
        sentinelConfig.setPassword(redisProperties.getPassword());
        sentinelConfig.setSentinelPassword(redisProperties.getSentinel().getPassword());
        sentinelConfig.setUsername(redisProperties.getUsername() != null && !redisProperties.getUsername().isEmpty() ? redisProperties.getUsername() : null);
        return sentinelConfig;
    }

    private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
        List<RedisNode> nodes = new ArrayList<>();
        for (String node : sentinel.getNodes()) {
            try {
                String[] parts = node.split(":");
                Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                nodes.add(new RedisNode(parts[0], Integer.parseInt(parts[1])));
            } catch (RuntimeException ex) {
                throw new IllegalStateException("Invalid redis sentinel property '" + node + "'", ex);
            }
        }
        return nodes;
    }


    private RedisClusterConfiguration clusterConfig(RedisProperties redisProperties) {
        RedisClusterConfiguration redisConfig = new RedisClusterConfiguration();
        List<String> nodes = redisProperties.getCluster().getNodes();
        for (String ipPort : nodes) {
            String[] ipPortArr = ipPort.split(":");
            redisConfig.clusterNode(ipPortArr[0], Integer.parseInt(ipPortArr[1].trim()));
        }
        redisConfig.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
        redisConfig.setPassword(redisProperties.getPassword());

        return redisConfig;
    }

    private MultiRedisProperties multiRedisProperties;

    @Override
    public void setEnvironment(Environment environment) {
        initMultiRedisProperties(environment);
    }

    private void initMultiRedisProperties(Environment environment) {
        multiRedisProperties = Binder.get(environment).bind(AnnotationUtils.getAnnotation(MultiRedisProperties.class, ConfigurationProperties.class).prefix(),
                MultiRedisProperties.class).get();
    }
}
